﻿// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Buffers;
using System.Collections.Generic;

namespace SnapObjects.Data
{
    internal class SysJsonStreamLinkedSegment
    {
        private int _holdStartIndex;
        private bool _isHoldSegment;
        private SysJsonSequenceSegment _holdSegment;

        private readonly int _segmentSize;

        public SysJsonStreamLinkedSegment(int segmentSize)
        {
            _segmentSize = segmentSize;
            _isHoldSegment = false;
            _holdSegment = null;
            _holdStartIndex = -1;
        }

        public SysJsonSequenceSegment FirstSegment { get; private set; }

        public SysJsonSequenceSegment LastSegment { get; private set; }

        public SysJsonSequenceSegment GetValidatedSegment(long consumedBytes)
        {
            var segment = SegmentPool.Get();

            if (segment == null)
            {
                segment = new SysJsonSequenceSegment(_segmentSize);
            }

            if (this.LastSegment == null)
            {
                segment.SetRunningIndex(consumedBytes);
            }
            else
            {
                segment.PreSegment = this.LastSegment;
            }

            var lastSegment = this.LastSegment;
            if (lastSegment != null)
            {
                lastSegment.NextSegment = segment;
            }

            this.LastSegment = segment;

            if (this.FirstSegment == null)
            {
                this.FirstSegment = segment;
            }

            this.FirstSegment.PreSegment = null;

            return segment;
        }

        public void UpdateStartIndex(long consumedBytes)
        {
            if (consumedBytes == 0)
            {
                return;
            }

            var segment = this.FirstSegment ?? throw new NullReferenceException();

            while (true)
            {
                segment.UpdateStartIndex(consumedBytes);

                if (segment.Done())
                {
                    if (!_isHoldSegment)
                    {
                        SegmentPool.Return(segment);
                    }

                    this.FirstSegment = segment.NextSegment;

                    if (segment == this.LastSegment)
                    {
                        this.FirstSegment = this.LastSegment = null;
                        break;
                    }
                }

                segment = segment.NextSegment;

                if (segment == null)
                {
                    break;
                }
            }
        }

        public void HoldSegment(long consumedBytes)
        {
            _holdSegment = this.FirstSegment;

            while (true)
            {
                if (_holdSegment.RunningIndex + _holdSegment.BufferCount >= consumedBytes)
                {
                    break;
                }

                if (_holdSegment.NextSegment == null)
                {
                    break;
                }

                _holdSegment = _holdSegment.NextSegment;
            }

            _holdStartIndex = (int)(consumedBytes - _holdSegment.RunningIndex);

            _isHoldSegment = true;
        }

        public ReadOnlySequence<byte> GetHoldSegmentSequence(long consumedBytes)
        {
            if (_holdSegment == null)
            {
                throw new NullReferenceException();
            }

            return new ReadOnlySequence<byte>(
                _holdSegment,
                _holdStartIndex - 1,
                this.LastSegment,
                (int)(consumedBytes - this.LastSegment.RunningIndex));
        }

        public void CancelHoldSegments(long consumedBytes)
        {
            if (_holdSegment == null)
            {
                throw new NullReferenceException();
            }

            if (!_isHoldSegment)
            {
                return;
            }

            this.FirstSegment = _holdSegment;
            if (this.FirstSegment != null)
            {
                this.FirstSegment.StartIndex = _holdStartIndex;
            }

            _isHoldSegment = false;

            this.UpdateStartIndex(consumedBytes);

            _holdSegment = null;
            _holdStartIndex = -1;
        }

        private static class SegmentPool
        {
            private readonly static Stack<SysJsonSequenceSegment> _segments = new Stack<SysJsonSequenceSegment>();

            public static SysJsonSequenceSegment Get()
            {
                SysJsonSequenceSegment segment = null;

                if (_segments.Count > 0)
                {
                    segment = _segments.Pop();

                    segment.Reset();
                }

                return segment;
            }

            public static void Return(SysJsonSequenceSegment segment)
            {
                if (segment == null)
                {
                    throw new ArgumentNullException(nameof(segment));
                }

                _segments.Push(segment);
            }
        }
    }
}
